import {expect, assert} from 'chai'
import {describe} from 'mocha'
import * as commitUtil from '../src/git-commit-message'
import {recordInfo, extractRepoName, calculateCommitRange} from '../src/git-commit-message'
import {GitTools} from '../src/git-command-helper'
import fs from 'fs'
import * as step from '@flow-step/step-toolkit'
import * as sinon from 'sinon'
import {IGitSourceParams} from '../src/git-source-params'
import * as path from 'node:path'
import * as util from '../src/util'
import exp = require('node:constants')


describe('测试<git提交信息>相关的逻辑', () => {
    it('测试buildCommit方法: git log输出信息正确转换为JSON格式', () => {
        let logMsg =
            '###commitId###:6ec1c5c0efb9aac63c341b7cb1abb6eb19c392da###commitTime###:Wed Mar 1 11:32:25 2023 +0000###commitAuthor###:lybc\\codes###commitMsg###:Initial commit'
        let result = commitUtil.buildCommit(logMsg).replace(/'/g, '')
        console.log(result)
        expect(result).equal(
            '{"commitId": "6ec1c5c0efb9aac63c341b7cb1abb6eb19c392da", "commitMsg": "Initial%20commit", "commitTime": "Wed Mar 1 11:32:25 2023 +0000", "commitAuthor": "lybc%5Ccodes"}'
        )
    })

    it('JSON数组转换的问题：正确', () => {
        let str =
            '[{"commitId": "46663", "commitMsg": "dhfia"}, {"commitId": "666", "commitMsg": "dhfuasidh"}]'
        let json = JSON.parse(str)
        assert.isObject(json[0], 'json should be an object')
    })
    it('JSON数组转换的问题：JSON解析异常', () => {
        let str =
            '["{"commitId": "46663", "commitMsg": "dhfia"}", "{"commitId": "666", "commitMsg": "dhfuasidh"}"]'
        try {
            JSON.parse(str)
        } catch (e) {
            console.log('捕获到异常，则说明解析错误!')
        }
    })

    it('获取仓库名称：正确', () => {
        let repo = 'git@example.com/namespace/repo.git'
        let sourceName = extractRepoName(repo)
        expect(sourceName).equal('repo')

        let repo2 = 'https://example.com/namespace/repo.git'
        let sourceName2 = extractRepoName(repo2)
        expect(sourceName2).equal('repo')

        let repo3 = 'https://user:pw@example.com/namespace/repo'
        let sourceName3 = extractRepoName(repo3)
        expect(sourceName3).equal('repo')
    })
})

describe('测试<git命令行工具>相关的逻辑', () => {
    let sandbox: sinon.SinonSandbox;
    let gitStub: sinon.SinonStubbedInstance<GitTools>;
    // 声明所有stub的类型
    let errorStub: sinon.SinonStub;
    let infoStub: sinon.SinonStub;

    beforeEach(() => {
        sandbox = sinon.createSandbox();
        gitStub = sandbox.createStubInstance(GitTools);

        // 模拟文件系统操作
        sandbox.stub(fs, 'writeFileSync');
        sandbox.stub(fs, 'appendFileSync');
        sandbox.stub(fs, 'existsSync').returns(true);
        sandbox.stub(fs, 'unlinkSync');

        // 模拟step工具方法
        infoStub = sandbox.stub(step, 'info');
        errorStub = sandbox.stub(step, 'error');
    });
    afterEach(() => {
        sandbox.restore();
    });

    it('模拟当previousCommitId存在且不合法时，执行git log命令失败的情况下（git log返回"fatal: invalid object name \'fatal\'."），应该重新获取最新一次提交的日志', async () => {
        // git log命令失败时，code_commit.log文件应该为空
        sandbox.stub(fs, 'readFileSync').returns('');

        const input = new IGitSourceParams();
        input.projectDir = '/tmp/';
        input.workDir = '/tmp/work';
        input.branchMode = false;
        input.previousCommitId = 'fatal: invalid';
        input.gitRepo = 'test-repo';
        input.repo = 'git@github.com:test/repo.git';
        input.type = 'git';
        input.repoType = 'git';
        input.name = 'test-repo';
        input.cloneUrl = 'git@github.com:test/repo.git';
        input.gitEndpoint = 'github.com';
        input.hostGitMethod = 'ssh';
        input.httpPolicy = 'default';
        input.httpUrlWithCredential = false;
        input.submodule = false;
        input.depth = 0;

        // 模拟git.getCommitId返回值
        gitStub.getCommitId.resolves({
            stdout: 'abc123',
            exitCode: 0
        });

        // 模拟git.log的两次不同返回值
        const logOutput = ['###commitId###:abc123###commitTime###:2024-03-20###commitAuthor###:test###commitMsg###:test commit'];
        // // 第一次调用返回空字符串
        // gitStub.log.onFirstCall().resolves({
        //     stdout: '',
        //     exitCode: 0
        // });
        // // 第二次调用返回有效的commit信息
        // gitStub.log.onSecondCall().resolves({
        //     stdout: logOutput,
        //     exitCode: 0
        // });

        // 使用stub替代onCall顺序调用方式(resolve是异步，Promise的解析不是立即完成的，可能会导致第二次调用时还没有正确设置返回值)
        gitStub.log.restore();
        const logStub = sandbox.stub(GitTools.prototype, 'log');
        // 第一次调用返回空字符串
        logStub.withArgs(input.projectDir, 20, sinon.match.any).resolves({
            stdout: [],
            exitCode: 0
        });
        // 第二次调用返回有效的commit信息
        logStub.withArgs(input.projectDir, 1, sinon.match.any).resolves({
            stdout: logOutput,
            exitCode: 0
        });


        await recordInfo(input, 1);

        // 打印所有的error调用信息
        // console.log('All error calls:', errorStub.getCalls().map(call => ({
        //     args: call.args,
        //     text: call.args[0]
        // })));
        // 验证错误信息是否被打印
        const hasErrorMessage = errorStub.getCalls().some(call =>
            call.args.some(arg =>
                arg && typeof arg === 'string' && arg.includes('Get git log failed, require the most recent commit info.')
            )
        );
        expect(hasErrorMessage, 'Should contain error message in any argument').to.be.true;

        // 验证git.log被调用了两次，使用不同的参数
        expect(logStub.calledTwice).to.be.true;
        expect(logStub.firstCall.args[1]).to.equal(20);  // 第一次调用请求20条记录
        expect(logStub.secondCall.args[1]).to.equal(1);   // 第二次调用请求1条记录
    });

    it('模拟当previousCommitId存在且合法，执行git log命令成功的情况', async () => {
        sandbox.stub(fs, 'readFileSync').returns('###commitId###:abc123###commitTime###:2024-03-20###commitAuthor###:test###commitMsg###:test commit');

        const input = new IGitSourceParams();
        input.projectDir = '/tmp/';
        input.workDir = '/tmp/work';
        input.branchMode = false;
        input.previousCommitId = '29cbeb9993eddc8cf55694e1e95bab913dca8340';
        input.gitRepo = 'test-repo';
        input.repo = 'git@github.com:test/repo.git';
        input.type = 'git';
        input.repoType = 'git';
        input.name = 'test-repo';
        input.cloneUrl = 'git@github.com:test/repo.git';
        input.gitEndpoint = 'github.com';
        input.hostGitMethod = 'ssh';
        input.httpPolicy = 'default';
        input.httpUrlWithCredential = false;
        input.submodule = false;
        input.depth = 0;

        await recordInfo(input, 1);

        // 验证日志信息是否被打印
        const hasInfoMessage = infoStub.getCalls().some(call =>
            call.args.some(arg =>
                arg && typeof arg === 'string' && arg.includes('Get git log success')
            )
        );
        expect(hasInfoMessage, 'Should contain success message in any argument').to.be.true;
    });
})

describe('test executeGitCommandWithOutputToArray method', () => {
    const projectDir = '/tmp';
    const logFilePath = path.join(projectDir, 'code_commit.log');
    let gitTools: GitTools;
    let callStub: sinon.SinonStub;

    beforeEach(() => {
        // 清空日志文件
        fs.writeFileSync(logFilePath, '', { flag: 'w' });

        gitTools = new GitTools();
        callStub = sinon.stub().resolves(0);
        // Mock call 方法
        sinon.replace(require('../src/exec/call'), 'call', callStub);
    });

    afterEach(() => {
        sinon.restore();
    });

    it('should return correct GitOutput when call succeed', async () => {
        const mockExitCode = 0;
        const mockStdout: string[] = [];
        mockStdout.push('###commitId###:706091e###commitMsg###:test,with comma###commitTime###:Tue Mar 19 21:27:38 2024 +0800###commitAuthor###:lyb');
        mockStdout.push('###commitId###:806091e###commitMsg###:test with enter###commitTime###:Tue Mar 19 21:27:38 2024 +0800###commitAuthor###:lyb');
        mockStdout.push('###commitId###:906091e###commitMsg###:test(3)commit message###commitTime###:Tue Mar 19 21:27:38 2024 +0800###commitAuthor###:lyb');
        mockStdout.push('###commitId###:606091e###commitMsg###:test\\with###commitTime###:Tue Mar 19 21:27:38 2024 +0800###commitAuthor###:lyb');
        //模拟 call 方法的返回值
        callStub.callsFake(async (command, params, options, { listener }) => {
            mockStdout.forEach(line => listener.stdoutLine(line));
            return mockExitCode;
        });

        const output = await gitTools.log('/tmp', 20, `###commitId###:%H###commitTime###:%cd###commitAuthor###:%an###commitMsg###:%s`);
        const content = output.stdout.join(util.EOL)
        fs.writeFileSync(
            logFilePath,
            output.stdout.join(util.EOL),
            {
                flag: 'a'
            })

        const fileContent = fs.readFileSync(logFilePath, 'utf-8');
        expect(fileContent.trim()).to.equal(output.stdout.join(util.EOL));
    });
})

describe('test calculateCommitRange method', () => {
    let gitToolsStub: sinon.SinonStubbedInstance<GitTools>;
    let fsWriteFileSyncStub: sinon.SinonStub;
    let fsAppendFileSyncStub: sinon.SinonStub;
    let stepInfoStub: sinon.SinonStub;
    let stepErrorStub: sinon.SinonStub;

    beforeEach(() => {
        gitToolsStub = sinon.createStubInstance(GitTools);
        // @ts-ignore
        sinon.stub(GitTools.prototype, 'getCommitId').returns(Promise.resolve({ stdout: 'currentCommitId' }));
        // @ts-ignore
        sinon.stub(GitTools.prototype, 'log').returns(Promise.resolve({ stdout: ['commitLog'] }));

        fsWriteFileSyncStub = sinon.stub(fs, 'writeFileSync');
        fsAppendFileSyncStub = sinon.stub(fs, 'appendFileSync');
        stepInfoStub = sinon.stub(step, 'info');
        stepErrorStub = sinon.stub(step, 'error');
    });

    afterEach(() => {
        sinon.restore();
    });

    it('should return early if previousCommitId is undefined', async () => {
        // @ts-ignore
        const input: IGitSourceParams = { projectDir: '/tmp/projectDir', workDir: '/tmp/workDir' };
        await calculateCommitRange(input);
        expect(gitToolsStub.getCommitId.called).to.be.false;
        expect(fsWriteFileSyncStub.called).to.be.false;
        expect(fsAppendFileSyncStub.called).to.be.false;
        expect(stepInfoStub.called).to.be.false;
    });

    it('should return early if branch is undefined', async () => {
        // @ts-ignore
        const input: IGitSourceParams = { projectDir: '/tmp/projectDir', workDir: '/tmp/workDir', previousCommitId: 'previousCommitId' };
        await calculateCommitRange(input);
        expect(gitToolsStub.getCommitId.called).to.be.false;
        expect(fsWriteFileSyncStub.called).to.be.false;
        expect(fsAppendFileSyncStub.called).to.be.false;
        expect(stepInfoStub.called).to.be.false;
    });

    it('should calculate commit range and record info', async () => {
        // @ts-ignore
        const input: IGitSourceParams = {gitRepo: 'repo', projectDir: '/tmp/projectDir', workDir: '/tmp/workDir', previousCommitId: 'previousCommitId', branch: 'branch' };
        await calculateCommitRange(input);
        expect(gitToolsStub.getCommitId.called).to.be.false;
        expect(fsWriteFileSyncStub.calledWith(`${input.workDir}${util.SEP}commitInfo`, `repo.branch.preCommitId=previousCommitId${util.EOL}`, { flag: 'a' })).to.be.true;
        expect(fsAppendFileSyncStub.calledWith(`${input.workDir}${util.SEP}commitInfo`, `repo.branch.lastCommitId=currentCommitId${util.EOL}`)).to.be.true;
        expect(stepInfoStub.calledWith('Calculating increasement commit scope: ')).to.be.true;
        expect(stepInfoStub.calledWith(`repo.branch.preCommitId=previousCommitId`)).to.be.true;
        expect(stepInfoStub.calledWith(`repo.branch.lastCommitId=currentCommitId`)).to.be.true;
    });
})

describe('test recordInfo method', () => {
    const projectDir = '/tmp';
    const logFilePath = path.join(projectDir, 'code_commit.log');
    let gitTools: GitTools;
    let callStub: sinon.SinonStub;

    let fsReadFileSyncStub: sinon.SinonStub;
    let fsAppendFileSyncStub: sinon.SinonStub;
    let stepInfoStub: sinon.SinonStub;
    let stepOutputStub: sinon.SinonStub;
    let stepExportEnvStub: sinon.SinonStub;

    beforeEach(() => {
        fs.writeFileSync(logFilePath, '', { flag: 'w' });

        gitTools = new GitTools();
        callStub = sinon.stub().resolves(0);
        // Mock call 方法
        sinon.replace(require('../src/exec/call'), 'call', callStub);


        fsReadFileSyncStub = sinon.stub(fs, 'readFileSync');
        fsAppendFileSyncStub = sinon.stub(fs, 'appendFileSync');
        stepInfoStub = sinon.stub(step, 'info');
        stepOutputStub =sinon.stub(step, 'addOutput')
        stepExportEnvStub = sinon.stub(step, 'exportEnvVar')
    });

    afterEach(() => {
        sinon.restore();
    });

    it('获取git log成功并写入环境变量', async () => {
        const input = new IGitSourceParams();
        input.projectDir = '/tmp';
        input.workDir = '/tmp';
        input.branchMode = false;
        input.previousCommitId = '29cbeb9993eddc8cf55694e1e95bab913dca8340';
        input.gitRepo = 'test-repo';
        input.repo = 'git@github.com:test/repo.git';
        input.type = 'git';
        input.repoType = 'git';
        input.name = 'test-repo';
        input.cloneUrl = 'git@github.com:test/repo.git';
        input.gitEndpoint = 'github.com';
        input.hostGitMethod = 'ssh';
        input.httpPolicy = 'default';
        input.httpUrlWithCredential = false;
        input.submodule = false;
        input.depth = 0;
        input.branch = 'main';

        const mockExitCode = 0;
        const mockStdout: string[] = [];
        mockStdout.push('###commitId###:706091e###commitMsg###:test,with comma###commitTime###:Tue Mar 19 21:27:38 2024 +0800###commitAuthor###:lyb');
        mockStdout.push('###commitId###:806091e###commitMsg###:test with enter###commitTime###:Tue Mar 19 21:27:38 2024 +0800###commitAuthor###:lyb');
        mockStdout.push('###commitId###:906091e###commitMsg###:test(3)commit message###commitTime###:Tue Mar 19 21:27:38 2024 +0800###commitAuthor###:lyb');
        mockStdout.push('###commitId###:606091e###commitMsg###:test\\with###commitTime###:Tue Mar 19 21:27:38 2024 +0800###commitAuthor###:lyb');
        //模拟 call 方法的返回值
        callStub.callsFake(async (command, params, options, { listener }) => {
            mockStdout.forEach(line => listener.stdoutLine(line));
            return mockExitCode;
        });

        const output = await gitTools.log('/tmp', 20, `###commitId###:%H###commitTime###:%cd###commitAuthor###:%an###commitMsg###:%s`);
        fs.writeFileSync(
            logFilePath,
            output.stdout.join(util.EOL),
            {
                flag: 'a'
            })
        fsReadFileSyncStub.returns(output.stdout.join(util.EOL));

        await recordInfo(input, 1);

        expect(stepInfoStub.calledWith('There are changes between the last commit of the pipeline and this commit')).to.be.true;
        expect(stepInfoStub.calledWith('Get git log success')).to.be.true;
        expect(stepOutputStub.callCount).equal(17);
        expect(stepExportEnvStub.callCount).equal(16);
    })


})
